package com.lx.core;

import com.lx.core.annotation.Value;
import com.lx.util.LX;
import com.lx.core.annotation.Bean;
import com.lx.core.annotation.Boot;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;

import static com.lx.core.Constant.*;

public class ApplicationContext {
    private Map<String,Object> ioc = new HashMap<>();//ioc容器
    private Set<Class<?>> classs = new HashSet<>(); //存放class对象
    private Set<String> scanPages = new HashSet<>();//存放已经扫描过的包名
    private Properties properties = new Properties();//存放配置文件
    public <T>List<T> getBean(Class<T> cls){
        return (List<T>) ioc.values().stream().filter(v -> {
            return cls.isAssignableFrom(v.getClass());
        }).collect(Collectors.toList());
    }
    public <T>T getBean(String name){
        return (T) ioc.get(name);
    }
    public Map<String,Object> getIoc(){ return ioc;}
    private void putObj(String name,Object obj){//存入对象
        if (name==null || name.length() == 0) name = obj.getClass().getSimpleName();
        if (ioc.containsKey(name)) throw new RuntimeException("对象名重复"+name);
        ioc.put(name,obj);
    }
    private Object getObj(String name,Class<?> cls){//获取对象
        if (name==null || name.length() == 0){
            name = cls.getSimpleName();
        }
        Object o = ioc.get(name);
        if (o == null){
            List<Object> ls = new ArrayList<>();
            ioc.values().forEach(v->{
                if (cls.isAssignableFrom(v.getClass())){
                    ls.add(v);
                }
            });
            if (ls.size() == 1){
                o = ls.get(0);
            }else{
                LX.exMsg(ls.size() == 0?"容器中不存在改类的实例!":"该类型对象存在多个实例:"+ls);
            }
        }
        return o;
    }

    public Properties getProperties() {
        return properties;
    }

    public static ApplicationContext run(Class<?> mainClass){
        try {
            long startTime = System.currentTimeMillis();
            LXLog.info(">>>>>>>>>>>>>>>>>>>>>>>>>>开始启动容器!<<<<<<<<<<<<<<<<<<<<<<<<<<");
            ApplicationContext ac = new ApplicationContext(mainClass);
            LXLog.info(">>>>>>>>>>>>>>>>>>>>>>>>>>容器启动完成!<<<<<<<<<<<<<<<<<<<<<<<<<<");
            LXLog.info(">>>>>>>>>>>>>>>>>>>>>>>>>>启动耗时:"+(System.currentTimeMillis()-startTime)+"ms!<<<<<<<<<<<<<<<<<<<<<<<<<<");
            return ac;
        } catch (Exception e) {
            LXLog.error("启动错误:",e);
            throw new RuntimeException(e);
        }
    }
    private ApplicationContext(Class<?> mainClass) throws Exception {
        putObj(null, this);
        //加载配置文件
        loadingConfigurationFile(mainClass,properties);
        LXLog.setLEVEL(properties.getProperty(SERVER_LOG_LEVEL,"INFO"));
        LXLog.debug("开始扫描ApplicationContext类所在包:["+ApplicationContext.class.getPackage().getName()+"]中的类!");
        classs.addAll(ScanPackage.scan(this.scanPages ,(cls)->{return cls.isAnnotationPresent(Bean.class);},ApplicationContext.class.getPackage().getName()));
        //加载配置文件里的类到容器
        classToContainerLoadingConfigurationFile(classs);
        LXLog.debug("开始扫描当前启动类所在包:["+mainClass.getPackage().getName()+"]中的类!");
        classs.addAll(ScanPackage.scan(this.scanPages,(cls)->{return cls.isAnnotationPresent(Bean.class);},mainClass.getPackage().getName()));
        if (mainClass.isAnnotationPresent(Boot.class)){
            String[] packageScan = mainClass.getAnnotation(Boot.class).packageScan();
            if (packageScan.length>0){
                LXLog.debug("开始扫描当前启动类注解指定包中的类!");
                classs.addAll(ScanPackage.scan(this.scanPages,(cls)->{return cls.isAnnotationPresent(Bean.class);},packageScan));
            }
        }
        createObj();
        fill();//填充对象 并调用初始化方法

    }

    //说明:加载启动类配置文件
    /**{ ylx } 2021/11/7 20:38 */
    private void loadingConfigurationFile(Class<?> mainClass, Properties properties) throws IOException {
        Properties netProperties = getNetProperties(mainClass.getClassLoader(), APPLICATION_PROPERTIES);
        netProperties.stringPropertyNames().forEach(key->{
            properties.setProperty(key,netProperties.getProperty(key));
        });
        //获取jar包外的配置文件
        String propertiesPath = System.getProperty("user.dir")+File.separator+JAR_APPLICATION_PROPERTIES;
        if (new File(propertiesPath).exists()){
            Properties netProperties1 = getNetProperties(propertiesPath);
            netProperties1.stringPropertyNames().forEach(key->{
                properties.setProperty(key,netProperties1.getProperty(key));
            });
        }
    }

    //说明:
    /**{ ylx } 2021/11/7 12:59 */
    private void classToContainerLoadingConfigurationFile(Set<Class<?>> classs) throws IOException, ClassNotFoundException {
        Properties netProperties = getNetProperties(ApplicationContext.class.getClassLoader(), BOOT_PROPERTIES);
        List<String> clss = Arrays.asList(netProperties.getProperty(BOOT_PROPERTIES_CONFIGURATION).split(","));
        for (String s : clss) {
            LXLog.debug("扫描到当前class:"+s);
            classs.add(Class.forName(s));
        }
    }

    //获取初始化信息
    private void createObj() throws Exception {
        LXLog.debug("加载类到IOC!");
        for (Class<?> cls:classs){
            LXLog.debug("当前实例化:"+cls.getName());
            Object o = cls.newInstance();//构造
            putObj(cls.isAnnotationPresent(Bean.class)?cls.getAnnotation(Bean.class).value():"",o);//添加到容器
            for (Method method : cls.getDeclaredMethods()){
                if (method.isAnnotationPresent(Bean.class)){
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    if (parameterTypes.length == 0){//方法没有参数
                        putObj(method.getAnnotation(Bean.class).value() ,method.invoke(o));
                    }else{
                        throw new Exception("函数不能设置参数!"+cls.getName()+"."+method.getName());
                    }
                }
            }
        }
        LXLog.debug("加载类完成!");
    }
    //填充bean的成员变量
    private void fill() throws Exception {
        LXLog.debug("填充类属性!");
        for (Object o : ioc.values()){
            LXLog.debug("正在填充属性:"+o.getClass().getName());
            for (Field f : o.getClass().getDeclaredFields()){
                if (f.isAnnotationPresent(Bean.class)){
                    boolean access = f.isAccessible();
                    f.setAccessible(true);
                    f.set(o,getObj(f.getAnnotation(Bean.class).value(),f.getType()));
                    f.setAccessible(access);
                }else if (f.isAnnotationPresent(Value.class)){
                    boolean access = f.isAccessible();
                    f.setAccessible(true);
                    String value = f.getAnnotation(Value.class).value();
                    LX.exObj(value,"@Value的key不能为空!"+o.getClass().getName()+":"+f.getName());
                    String[] keys = value.split(":");
                    String fvalue = properties.getProperty(keys[0]);
                    if (LX.isEmpty(fvalue)){
                        LX.exMsg(keys.length!=2,"请检查@Value配置:"+o.getClass().getName()+":"+f.getName());
                        fvalue = keys[1];
                    }
                    LX.exObj(fvalue,"获取到的配置为空!"+o.getClass().getName()+":"+f.getName());
                    switch (f.getType().getName()){
                        case "java.lang.String":
                            f.set(o,fvalue);
                            break;
                        case "int":
                        case "java.lang.Integer":
                            f.set(o,Integer.parseInt(fvalue));
                            break;
                        case "long":
                        case "java.lang.Long":
                            f.set(o,Long.parseLong(fvalue));
                        default:
                            LX.exMsg("暂不支持其他类型转换");

                    }
                    f.setAccessible(access);
                }
            }
            Bean bean = o.getClass().getAnnotation(Bean.class);
            if (bean == null)continue;
            String init = bean.init();
            if (!"".equals(init)){//调用初始化方法
                Method[] methods = o.getClass().getDeclaredMethods();
                for (Method m : methods){
                    if (m.getName().equals(init)){
                        LXLog.debug("开始调用初始化方法:"+o.getClass().getName()+"-"+m.getName());
                        Class<?>[] types = m.getParameterTypes();
                        if (types.length>0){
                            Object [] objs = new Object[types.length];
                            for (int i = 0;i<types.length;i++){
                                objs[i] = getObj(null,types[i]);
                            }
                            m.invoke(o,objs);//调用初始化方法
                        }else{
                            m.invoke(o);//调用初始化方法
                        }
                    }
                }

            }
        }
        LXLog.debug("填充属性完成");
    }

    //说明:加载配置文件
    /**{ ylx } 2021/11/7 11:25 */
    public static Properties getNetProperties(String path) throws IOException {
        Properties properties = new Properties();
        properties.load(new FileInputStream(path));
        return properties;
    }
    //说明:加载配置文件
    /**{ ylx } 2021/11/7 11:25 */
    public static Properties getNetProperties(ClassLoader classLoader,String path) throws IOException {
        Enumeration<URL> urls = classLoader.getResources(path);
        Properties properties = new Properties();
        if (urls.hasMoreElements()){
            properties.load(urls.nextElement().openConnection().getInputStream());
        }
        return properties;
    }

}
